package com.yida.common.core.ukey;

import java.math.BigInteger;

/**
 * base class for points on elliptic curves.
 */
public abstract class ECPoint {
    ECCurve curve;
    ECFieldElement x;
    ECFieldElement y;

    protected ECPoint(ECCurve curve, ECFieldElement x, ECFieldElement y) {
        this.curve = curve;
        this.x = x;
        this.y = y;
    }

    public ECFieldElement getX() {
        return x;
    }

    public ECFieldElement getY() {
        return y;
    }

    public boolean equals(Object other) {
        if (other == this)
            return true;

        if (!(other instanceof ECPoint))
            return false;

        ECPoint o = (ECPoint)other;

        return x.equals(o.x) && y.equals(o.y);
    }

    public abstract byte[] getEncoded();

    public abstract ECPoint add(ECPoint b);

    public abstract ECPoint subtract(ECPoint b);

    public abstract ECPoint twice();

    public abstract ECPoint multiply(BigInteger b);

    /**
     * Elliptic curve points over Fp
     */
    public static class Fp extends ECPoint {
        public Fp(ECCurve curve, ECFieldElement x, ECFieldElement y) {
            super(curve, x, y);
        }

        /**
         * return the field element encoded with point compression. (S 4.3.6)
         */
        public byte[] getEncoded() {
            byte PC;

            if (this.getY().toBigInteger().testBit(0)) {
                PC = 0x02;
            } else {
                PC = 0x03;
            }

            byte[] X = this.getX().toBigInteger().toByteArray();
            byte[] PO = new byte[X.length + 1];

            PO[0] = PC;
            System.arraycopy(X, 0, PO, 1, X.length);

            return PO;
        }

        // B.3 pg 62
        public ECPoint add(ECPoint b) {
            ECFieldElement gamma = b.y.subtract(y).divide(b.x.subtract(x));

            ECFieldElement x3 = gamma.multiply(gamma).subtract(x).subtract(b.x);
            ECFieldElement y3 = gamma.multiply(x.subtract(x3)).subtract(y);

            return new Fp(curve, x3, y3);
        }

        // B.3 pg 62
        public ECPoint twice() {
            ECFieldElement TWO = curve.fromBigInteger(BigInteger.valueOf(2));
            ECFieldElement THREE = curve.fromBigInteger(BigInteger.valueOf(3));
            ECFieldElement gamma = x.multiply(x).multiply(THREE).add(curve.a).divide(y.multiply(TWO));

            ECFieldElement x3 = gamma.multiply(gamma).subtract(x.multiply(TWO));
            ECFieldElement y3 = gamma.multiply(x.subtract(x3)).subtract(y);

            return new Fp(curve, x3, y3);
        }

        // D.3.2 pg 102 (see Note:)
        public ECPoint subtract(ECPoint p2) {
            return add(new Fp(curve, p2.x, p2.y.negate()));
        }

        // D.3.2 pg 101
        public ECPoint multiply(BigInteger k) {
            // BigInteger e = k.mod(n); // n == order this
            BigInteger e = k;

            BigInteger h = e.multiply(BigInteger.valueOf(3));

            ECPoint R = this;

            for (int i = h.bitLength() - 2; i > 0; i--) {
                R = R.twice();

                if (h.testBit(i) && !e.testBit(i)) {
                    // System.out.print("+");
                    R = R.add(this);
                } else if (!h.testBit(i) && e.testBit(i)) {
                    // System.out.print("-");
                    R = R.subtract(this);
                }
                // else
                // System.out.print(".");
            }
            // System.out.println();

            return R;
        }
    }
}
